package com.screencapture.slate;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.KeyStroke;

import com.screencapture.core.ScreenCapture;
import com.screencapture.interfaces.ImageToClipboard;
import com.screencapture.utilities.AppConstants;
import com.screencapture.utilities.AppUtils;

public class Slate {
        
        JFrame scEditor;
        Point prevPoint, presentPoint;
        BufferedImage canvas;
        BufferedImage background;
        BufferedImage currentStroke;
        Graphics2D canvasGraphics;
        Graphics2D currentStrokeGraphics;
        Point translatedPointForDrawing;
        int activeTool = 2;
        Color brushColor = Color.red;
        Shape presentShape;
        
        SlateStateMaintainance presentState = new SlateStateMaintainance();
        SlateStateMaintainance startState = presentState;

        public Slate(BufferedImage toEdit) {
                background = toEdit;
        }

        public void init() {

                if(scEditor == null){
                        scEditor = new JFrame("Slate");
                        
                        scEditor.addWindowListener(new WindowAdapter() {
                                @Override
                                public void windowClosing(WindowEvent arg0) {
                                        ScreenCapture.showMe();
                                        scEditor.setVisible(false);
                                        
                                        canvas = background = null;
                                        presentState = startState = null;                               
                                }
                        });
                        
                        scEditor.setLayout(new BorderLayout());
                        
                        JLayeredPane layers = new JLayeredPane();
                        layers.setPreferredSize(new Dimension(background.getWidth(), background.getHeight()));
        
                        final JPanel backgroundPane = new JPanel() {
                                private static final long serialVersionUID = 9221732976381952814L;
        
                                @Override
                                protected void paintComponent(Graphics g) {
                                        super.paintComponent(g);
                                        g.drawImage(background, 0, 0, null);
                                }
                        };
                        backgroundPane.setSize(background.getWidth(), background.getHeight());
                        layers.add(backgroundPane, 0);
                        
                        final JPanel drawingPane = new JPanel() {
                                private static final long serialVersionUID = 3148326175058470494L;
        
                                @Override
                                protected void paintComponent(Graphics g) {
                                        super.paintComponent(g);
                                        g.drawImage(canvas, 0, 0, null);
                                }
                        };
                        drawingPane.setOpaque(false);
                        drawingPane.setSize(background.getWidth(), background.getHeight());
                        layers.add(drawingPane, 0);
                        
                        final JPanel currentStrokePane = new JPanel() {
                                private static final long serialVersionUID = -2499655739116655851L;
        
                                @Override
                                protected void paintComponent(Graphics g) {
                                        super.paintComponent(g);
                                        g.drawImage(currentStroke, 0, 0, null);
                                }
                        };
                        
                        currentStrokePane.setOpaque(false);
                        currentStrokePane.setSize(background.getWidth(), background.getHeight());
                        layers.add(currentStrokePane, 0);
        
                        JPanel topPanel = new JPanel();
                        
                        ActionListener topPanelActionListener = new ActionListener() {
                                
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                        String label = ((JButton) e.getSource()).getText();
                                        
                                        if(label.equalsIgnoreCase("Undo")){
                                                traverseInState(-1);
                                                drawingPane.repaint();
                                        }else if(label.equalsIgnoreCase("Redo")){
                                                traverseInState(1);
                                                drawingPane.repaint();
                                        }else if(label.equalsIgnoreCase("Save States")){
                                                SlateStateMaintainance tempState = startState;
                                                int i = 1;
                                                while (tempState != null) {
                                                        try {
                                                                BufferedImage img = new BufferedImage(canvas.getWidth(), canvas.getHeight(), BufferedImage.TYPE_INT_ARGB);
                                                                tempState.paint(img.createGraphics());
                                                                ImageIO.write(img, "png", new File(i + ".png"));
                                                        } catch (IOException ex) {
                                                                ex.printStackTrace();
                                                        }
                                                        i++;
                                                        tempState = tempState.nextState;
                                                }
                                        }else if(label.equalsIgnoreCase("Copy")){
                                                BufferedImage image = AppUtils.mergeImages(background, canvas);
                                                ImageToClipboard imgSel = new ImageToClipboard(image);
                                                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(imgSel, null);
                                        }else if(label.equalsIgnoreCase("GC")){
                                                System.gc();
                                        }
                                }
                        };
                        
                        final JButton undo = new JButton("Undo");
                        undo.addActionListener(topPanelActionListener);
                        JButton redo = new JButton("Redo");
                        redo.addActionListener(topPanelActionListener);
                        JButton saveAllStates = new JButton("Save States");
                        saveAllStates.addActionListener(topPanelActionListener);
                        JButton copyToCliboard = new JButton("Copy");
                        copyToCliboard.addActionListener(topPanelActionListener);
                        JButton GC = new JButton("GC");
                        GC.addActionListener(topPanelActionListener);
                        
                        topPanel.setLayout(new GridLayout(1, 6));
                        topPanel.add(undo);
                        topPanel.add(redo);
                        topPanel.add(saveAllStates);
                        topPanel.add(copyToCliboard);
                        topPanel.add(GC);
                        
                        // Set keyboard shortcuts
                        //Undo
                        undo.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control Z"), "undo");
                        undo.getActionMap().put("undo", new AbstractAction() {
                                private static final long serialVersionUID = -3369003302369106818L;
        
                                @Override
                                public void actionPerformed(ActionEvent arg0) {
                                        traverseInState(-1);
                                        drawingPane.repaint();
                                }
                        });
                        //Redo
                        redo.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control Y"), "redo");
                        redo.getActionMap().put("redo", new AbstractAction() {
                                private static final long serialVersionUID = 8882357255077449769L;
        
                                @Override
                                public void actionPerformed(ActionEvent arg0) {
                                        traverseInState(1);
                                        drawingPane.repaint();
                                }
                        });
                        //Copy
                        copyToCliboard.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control C"), "copy");
                        copyToCliboard.getActionMap().put("copy", new AbstractAction() {
                                private static final long serialVersionUID = -6787632332206209098L;
        
                                @Override
                                public void actionPerformed(ActionEvent arg0) {
                                        BufferedImage image = AppUtils.mergeImages(background, canvas);
                                        ImageToClipboard imgSel = new ImageToClipboard(image);
                                        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(imgSel, null);
                                }
                        });
                        
                        scEditor.add(topPanel, BorderLayout.NORTH);
        
                        // The toolbox
                        JPanel toolPanel = buildToolPanel();
                        scEditor.add(toolPanel, BorderLayout.EAST);             
                        
                        
                        layers.addMouseMotionListener(new MouseMotionAdapter() {
        
                                @Override
                                public void mouseDragged(MouseEvent e) {
                                        super.mouseDragged(e);
                                        
                                        currentStrokeGraphics.setColor(brushColor);
                                        
                                        switch (activeTool) {
                                        case AppConstants.FREEHAND:
                                        {
                                                prevPoint = presentPoint;
                                                presentPoint = e.getPoint();
                                                Line2D.Float line = new Line2D.Float(AppUtils.diffPoint(prevPoint, translatedPointForDrawing), AppUtils.diffPoint(presentPoint, translatedPointForDrawing));
                                                if(presentShape == null){
                                                        presentShape = new GeneralPath();
                                                }
                                                ((GeneralPath) presentShape).append(line, true);
                                                currentStrokeGraphics.draw(line);
                                                break;
                                        }
                                        case AppConstants.LINE:
                                        {
                                                presentPoint = e.getPoint();
                                                AppUtils.clearImage(currentStroke);
                                                Line2D.Float line = new Line2D.Float(AppUtils.diffPoint(prevPoint, translatedPointForDrawing), AppUtils.diffPoint(presentPoint, translatedPointForDrawing));
                                                presentShape = line;
                                                currentStrokeGraphics.draw(line);
                                                break;
                                        }
                                        case AppConstants.RECTANGLE:
                                        {
                                                presentPoint = e.getPoint();
                                                AppUtils.clearImage(currentStroke);
                                                Rectangle rectangle = buildRectangle(AppUtils.diffPoint(prevPoint, translatedPointForDrawing), AppUtils.diffPoint(presentPoint, translatedPointForDrawing));
                                                presentShape = rectangle;
                                                currentStrokeGraphics.draw(rectangle);
                                                break;
                                        }
                                        case AppConstants.ROUNDEDRECTANGLE:
                                        {
                                                presentPoint = e.getPoint();
                                                AppUtils.clearImage(currentStroke);
                                                Rectangle rectangle = buildRectangle(AppUtils.diffPoint(prevPoint, translatedPointForDrawing), AppUtils.diffPoint(presentPoint, translatedPointForDrawing));
                                                RoundRectangle2D.Float roundedRectangle = new RoundRectangle2D.Float();
                                                roundedRectangle.setFrame(rectangle);
                                                roundedRectangle.archeight = roundedRectangle.arcwidth = 20F;
                                                presentShape = roundedRectangle;
                                                currentStrokeGraphics.draw(roundedRectangle);
                                                break;
                                        }
                                        case AppConstants.ELLIPSE:
                                        {
                                                presentPoint = e.getPoint();
                                                AppUtils.clearImage(currentStroke);
                                                Rectangle rectangle = buildRectangle(AppUtils.diffPoint(prevPoint, translatedPointForDrawing), AppUtils.diffPoint(presentPoint, translatedPointForDrawing));
                                                Ellipse2D.Float ellipse = new Ellipse2D.Float();
                                                ellipse.setFrame(rectangle);
                                                presentShape = ellipse;
                                                currentStrokeGraphics.draw(ellipse);
                                                break;
                                        }
                                        }
                                        currentStrokePane.repaint();
                                }
                        });
        
                        layers.addMouseListener(new MouseAdapter() {
                                @Override
                                public void mouseReleased(MouseEvent e) {
                                        
                                        canvasGraphics.drawImage(currentStroke, 0, 0, null);
                                        drawingPane.repaint();
                                        AppUtils.clearImage(currentStroke);
                                        currentStrokePane.repaint();
                                        
                                        prevPoint = presentPoint = null;
                                        if(activeTool != 0){
                                                recordState();
                                                removenthStateLink();
                                        }
                                        
                                        super.mouseReleased(e);
                                }
                                
                                @Override
                                public void mousePressed(MouseEvent e) {
                                        
                                        prevPoint = presentPoint;
                                        if (prevPoint == null) {
                                                prevPoint = e.getPoint();
                                        }
                                        presentPoint = e.getPoint();
                                        super.mousePressed(e);
                                }
                                
                        });
        
                        canvas = new BufferedImage(background.getWidth(), background.getHeight(), BufferedImage.TYPE_INT_ARGB);
                        canvasGraphics = canvas.createGraphics();
                        canvasGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                        RenderingHints.VALUE_ANTIALIAS_ON);
                        
                        currentStroke = new BufferedImage(background.getWidth(), background.getHeight(), BufferedImage.TYPE_INT_ARGB);
                        currentStrokeGraphics = currentStroke.createGraphics();
                        currentStrokeGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                        RenderingHints.VALUE_ANTIALIAS_ON);
                        
                        final JScrollPane scrollPane = new JScrollPane(layers);
                        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
                        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
                        scrollPane.addComponentListener(new ComponentAdapter() {
                                @Override
                                public void componentResized(ComponentEvent e) {
                                        super.componentResized(e);
                                        
                                        // Calculate left and top
                                        int left = ( - background.getWidth() + scrollPane.getWidth()) / 2;
                                        int top = ( - background.getHeight() + scrollPane.getHeight()) / 2;
                                        
                                        top = (top > 0) ? top : 0;
                                        left = (left > 0) ? left : 0;
                                        
                                        translatedPointForDrawing = new Point(left, top);
                                        
                                        backgroundPane.setLocation(translatedPointForDrawing);
                                        drawingPane.setLocation(translatedPointForDrawing);
                                        currentStrokePane.setLocation(translatedPointForDrawing);
                                }
                                
                        });
                        
                        scEditor.add(scrollPane, BorderLayout.CENTER);
                        scEditor.setSize(600, 600);
                        scEditor.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
                        scEditor.setLocationRelativeTo(null);
                }
                scEditor.setVisible(true);
                
        }

        private JPanel buildToolPanel() {
                final JPanel toolPanel = new JPanel();
                toolPanel.setLayout(new BoxLayout(toolPanel, BoxLayout.Y_AXIS));
                
                ActionListener actionListener = new ActionListener() {
                        
                        @Override
                        public void actionPerformed(ActionEvent e) {
                        		AbstractButton control = (AbstractButton) e.getSource();
                                int toolType = (Integer) control.getClientProperty("toolType");
                                
                                switch(toolType){
                                case AppConstants.COLOR:
                                {
                                        Color newColor = JColorChooser.showDialog(toolPanel,
                                                        "Choose Brush Color", brushColor);
                                        brushColor = newColor == null ? brushColor : newColor;
                                        BufferedImage icon = AppUtils.getImagePatch(15, 15, brushColor, true);
                                        control.setIcon(new ImageIcon(icon));
                                        break;
                                }
                                case AppConstants.FREEHAND:
                                {
                                        activeTool = AppConstants.FREEHAND;
                                        break;
                                }
                                case AppConstants.LINE:
                                {
                                        activeTool = AppConstants.LINE;
                                        break;
                                }
                                case AppConstants.RECTANGLE:
                                {
                                        activeTool = AppConstants.RECTANGLE;
                                        break;
                                }
                                case AppConstants.ROUNDEDRECTANGLE:
                                {
                                        activeTool = AppConstants.ROUNDEDRECTANGLE;
                                        break;
                                }
                                case AppConstants.ELLIPSE:
                                {
                                        activeTool = AppConstants.ELLIPSE;
                                        break;
                                }
                                }
                        }
                };
                
                JLabel toolkitLabel = new JLabel("Toolkit");
                
                JButton color = new JButton();
                color.putClientProperty("toolType", AppConstants.COLOR);
                BufferedImage icon = AppUtils.getImagePatch(15, 15, brushColor, true);
                color.setIcon(new ImageIcon(icon));
                color.setMargin(new Insets(6, 6, 6, 6));
                color.addActionListener(actionListener);
                
                ButtonGroup drawingTools = new ButtonGroup();
                
                JToggleButton freeHand = new JToggleButton();
                freeHand.setSelected(true);
                freeHand.putClientProperty("toolType", AppConstants.FREEHAND);
                freeHand.setMnemonic('f');
                freeHand.setFocusPainted(false);
                freeHand.setIcon(new ImageIcon("icons/freehand.png"));
                freeHand.setMargin(new Insets(2, 2, 2, 2));
                freeHand.addActionListener(actionListener);
                drawingTools.add(freeHand);
                
                JToggleButton lineTool = new JToggleButton();
                lineTool.putClientProperty("toolType", AppConstants.LINE);
                lineTool.setMnemonic('l');
                lineTool.setFocusPainted(false);
                lineTool.setIcon(new ImageIcon("icons/line.png"));
                lineTool.setMargin(new Insets(2, 2, 2, 2));
                lineTool.addActionListener(actionListener);
                drawingTools.add(lineTool);
                
                JToggleButton rectangleTool = new JToggleButton();
                rectangleTool.putClientProperty("toolType", AppConstants.RECTANGLE);
                rectangleTool.setMnemonic('r');
                rectangleTool.setFocusPainted(false);
                rectangleTool.setIcon(new ImageIcon("icons/rectangle.png"));
                rectangleTool.setMargin(new Insets(2, 2, 2, 2));
                rectangleTool.addActionListener(actionListener);
                drawingTools.add(rectangleTool);
                
                JToggleButton roundedRectangle = new JToggleButton();
                roundedRectangle.putClientProperty("toolType", AppConstants.ROUNDEDRECTANGLE);
                roundedRectangle.setMnemonic('t');
                roundedRectangle.setFocusPainted(false);
                roundedRectangle.setIcon(new ImageIcon("icons/roundedrectangle.png"));
                roundedRectangle.setMargin(new Insets(2, 2, 2, 2));
                roundedRectangle.addActionListener(actionListener);
                drawingTools.add(roundedRectangle);
                
                JToggleButton ellipseTool = new JToggleButton();
                ellipseTool.putClientProperty("toolType", AppConstants.ELLIPSE);
                ellipseTool.setMnemonic('e');
                ellipseTool.setFocusPainted(false);
                ellipseTool.setIcon(new ImageIcon("icons/ellipse.png"));
                ellipseTool.setMargin(new Insets(2, 2, 2, 2));
                ellipseTool.addActionListener(actionListener);
                drawingTools.add(ellipseTool);
                
                toolPanel.add(toolkitLabel);
                toolPanel.add(color);
                toolPanel.add(freeHand);
                toolPanel.add(lineTool);
                toolPanel.add(rectangleTool);
                toolPanel.add(roundedRectangle);
                toolPanel.add(ellipseTool);
                
                return toolPanel;
        }

        protected void traverseInState(int dir) {
                if (dir == -1) {
                        if (presentState.previousState != null) {
                                //canvas.setData(presentState.state.getData());
                                AppUtils.clearImage(canvas);
                                SlateStateMaintainance tempState = startState;
                                while(tempState != presentState){
                                        tempState.paint(canvasGraphics);
                                        tempState = tempState.nextState;
                                }
                                presentState = presentState.previousState;
                        }
                } else {
                        if (presentState.nextState != null) {
                                presentState = presentState.nextState;
                                //canvas.setData(presentState.state.getData());
                                AppUtils.clearImage(canvas);
                                SlateStateMaintainance tempState = startState;
                                while(tempState != presentState.nextState){
                                        tempState.paint(canvasGraphics);
                                        tempState = tempState.nextState;
                                }
                        }
                }
        }

        protected void recordState() {
                SlateStateMaintainance st = new SlateStateMaintainance();
                st.previousState = presentState;
                if (presentState.nextState != null) {
                        presentState.nextState.previousState = null;
                }
                presentState.nextState = st;
                presentState = st;
                try {
                        presentState.color = brushColor;
                        presentState.fill = (activeTool > 6);
                        presentState.shape = presentShape;
                        presentShape = null;
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }

        @SuppressWarnings("unused")
		protected void removenthStateLink() {
        	if(AppConstants.MAXSTATES > 0){
                int i = 0;
                SlateStateMaintainance st = presentState;
                while (i++ < AppConstants.MAXSTATES && st != null) {
                        st = st.previousState;
                }
                if(st != null && st.previousState != null){
                	st.previousState.nextState = null;
                	st.previousState = null;
                }
        	}
               
        }
        
        private Rectangle buildRectangle(Point p1, Point p2) {
                if (p1.x < p2.x && p1.y < p2.y) {
                        return new Rectangle(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
                } else if (p1.x < p2.x && p1.y > p2.y) {
                        return new Rectangle(p1.x, p2.y, p2.x - p1.x, p1.y - p2.y);
                } else if (p1.x > p2.x && p1.y > p2.y) {
                        return new Rectangle(p2.x, p2.y, p1.x - p2.x, p1.y - p2.y);
                } else {
                        return new Rectangle(p2.x, p1.y, p1.x - p2.x, p2.y - p1.y);
                }
        }

}